package com.googlecode.cswish.struts;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.log4j.Logger;

import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.PageElement;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.util.ActionContextHelper;
import com.googlecode.cswish.util.BlankObject;
import com.googlecode.cswish.util.FrameConstant;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.validator.ValidatorConfig;

/**
 * 1. Read the DB information from Model annotations. <br>
 * 2. Create the validate rule according to the DB information
 * <p>
 * (com.opensymphony.xwork2.validator.validators.default.xml)
 * 
 * @author Jerry.Feng Date: 2005-3-30 13:29:53
 * @version
 */
//@Component
@Deprecated
public class AutoValidator {
	private static final Logger logger = Logger.getLogger(AutoValidator.class);
	
	@Resource
	private BeanIntrospector beanIntrospector;
	
	public List<ValidatorConfig> getDefaultValidation(String prefix, String modelClassName,
			String method, PageElements ps, boolean isArray) {
		if (method.startsWith(FrameConstant.ACTION_PREPARE)) {
			// Don't validate it?
			return BlankObject.List;
		} else {
			boolean isAdd = RestAction.isCreate(null, method);
			boolean isUpdate = RestAction.isUpdate(null, method);
			if (isAdd || isUpdate) {
				return getValidationByModelInfo(prefix, modelClassName, ps, isUpdate, false, isArray);
			} else {
				return getValidationByModelInfo(prefix, modelClassName, ps, isUpdate, true, isArray);
			}
		}
	}

	/**
	 * convert the annotations to List<ValidatorConfig>
	 * 
	 * @param modelClassName
	 * @param isQuery 
	 * @return
	 * @see com.opensymphony.xwork2.validator.validators.default.xml
	 */
	private List<ValidatorConfig> getValidationByModelInfo(String prefix, String modelClassName, PageElements ps,
			boolean isUpdate, boolean isSearch, boolean isArray) {
		List<ValidatorConfig> configList = new ArrayList<ValidatorConfig>();
		
		Map<String, PageElement> elements = ps.getAllElements();
		ActionContext context = ActionContext.getContext();
		Class clazz = context.getActionInvocation().getAction().getClass();
		Locale locale = context.getLocale();
		ModelInfo modelInfo = beanIntrospector.getModelInfo(modelClassName);
		
		// FIXME: 
		/*
		Iterator<PropertyInfo> iter = modelInfo.getInterfaceProperties().iterator();
		while (iter.hasNext()) {
			PropertyInfo propertyInfo = iter.next();
			
			if (propertyInfo.isSimpleObj()) {
				String fullFieldName = prefix + propertyInfo.getName();
				// the following annotations information can be used for validation
				// Column, Id etc
				if (!isSearch && propertyInfo.isTableColumn()) {
					// 1. validate length
					int len = propertyInfo.getLength();
					if (len > 0 && String.class.getName().equals(propertyInfo.getType())) {
						String type = "stringlength";
						// field name must be less than ${max}
						String message = getText(clazz, "errors.maxlength", locale,
								getText(clazz, fullFieldName, locale), String.valueOf(len));
						
						Map<String, String> parameters = new HashMap<String, String>();
						parameters.put("maxLength", String.valueOf(len));
						ValidatorConfig config = buildConfig(type, isArray, prefix,
								propertyInfo.getName(), message, parameters);
						configList.add(config);
					}
					// 2. validate unique
					if (propertyInfo.isUnique()) {
						// ExceptionMappingInterceptor will catch the error information
					}
					 
					// 3. validate not null
					// 1) for non-nullable filed, it's required
					// 2) for non-auto increased id, it's required
					// 3) for update page, the top id is required
					// filter the required validators according to PageElements
					if (elements.get(fullFieldName) != null) {
						if (!propertyInfo.isNullable() && !propertyInfo.isId()
								|| propertyInfo.isId() && !propertyInfo.isAutoId()
								|| isUpdate && prefix.length() == 0) {
							String type;
							if (String.class.getName().equals(propertyInfo.getType())) {
								type = "requiredstring";
							} else {
								type = "required";
							}					
							String message = getText(clazz, "errors." + type, locale, 
									getText(clazz, fullFieldName, locale));
							
							Map<String, String> parameters = new HashMap<String, String>();
							ValidatorConfig config = buildConfig(type, isArray, prefix,
									propertyInfo.getName(), message, parameters);
							configList.add(config);
						}
					}
				}
				
				// 4. validate the data type
				Class type = propertyInfo.getTypeClass();
				if (type != null) {
					if (Date.class.isAssignableFrom(type)) {
						// FIXME: Get data format from DateConversion: datePattern=date.format
						// Ref: "struts.date.format" / SimpleDateFormat.toPattern()
						Object[] params = null;
						String dateFormat = context.getValueStack().findString("getText('struts.date.format')");
						// Use DateFormat.DEFAULT if the format is null?
						if (dateFormat != null && !"struts.date.format".equals(dateFormat)) {
							Calendar today = Calendar.getInstance();
							String dateExample = new SimpleDateFormat(dateFormat, locale).format(today.getTime());
							String message = getText(clazz, "errors.date", locale, 
									getText(clazz, fullFieldName, locale), dateExample);
							Map<String, String> parameters = new HashMap<String, String>();
							parameters.put("expression", "^(" + convetDateFormat2Regex(dateFormat) + ")?$");
							ValidatorConfig config = buildConfig("regex", isArray, prefix,
									propertyInfo.getName(), message, parameters);
							configList.add(config);
						}
					} else if (Number.class.isAssignableFrom(type)) {
						// TODO: support more detail type: long, float, big decimal etc 
						String numberFormat = "^((-?\\d+)(\\.\\d+)?)?$";	// In fact, it's a float format
						String message = getText(clazz, "errors.number", locale, 
								getText(clazz, fullFieldName, locale));
						
						Map<String, String> parameters = new HashMap<String, String>();
						parameters.put("expression", numberFormat);
						ValidatorConfig config = buildConfig("regex", isArray, prefix,
								propertyInfo.getName(), message, parameters);
						configList.add(config);
					}
				}
			}
		}//for each method
		*/
		return configList;
	}
	
	private String convetDateFormat2Regex(String dateFormat) {
		// FIXME: Here is only a simple implement
		StringBuilder sb = new StringBuilder();
		for (int i = 0, len = dateFormat.length(); i < len; i++) {
			char c = dateFormat.charAt(i);
			
			if (c == '\\') {
				sb.append("\\\\");
				
			} else if (c == 'y' || c == 'M' || c == 'w' || c == 'W' ||
					c == 'D' || c == 'd' || c == 'F' || c == 'H' ||
					c == 'k' || c == 'K' || c == 'h' || c == 'm' ||
					c == 's' || c == 'S') {
				sb.append("\\d");
			} else {
				sb.append(c);
			}
		}
		return sb.toString();
	}
	
	private ValidatorConfig buildConfig(String validateType, boolean isCollection,
			String prefix, String fieldName, String message, Map<String, String> parameters) {
		ValidatorConfig.Builder builder;
		
		if (isCollection) {
			builder = new ValidatorConfig.Builder("collection");
			builder.addParam("extParam.validatorType", validateType);
			// skip '[0].'
			builder.addParam("prefix", prefix.substring(0, prefix.length() - 4));
			builder.addParam("extParam.fieldName", fieldName);
		} else {
			builder = new ValidatorConfig.Builder(validateType);
		}
		
		builder.defaultMessage(message);
		builder.addParam("fieldName", prefix + fieldName);
		if (isCollection) {
			for (String key : parameters.keySet()) {
				builder.addParam("extParam." + key, parameters.get(key));
			}
		} else {
			builder.addParams(new HashMap<String, Object>(parameters));
		}
		return builder.build();
	}

	private String getText(Class clazz, String str, Locale locale, Object... args) {
		int begin = 0;
		int end = -3;
		StringBuilder formatStr = new StringBuilder();
		do {
			begin = end + 3;
			end = str.indexOf("[0]", begin);
			if (end > 0) {
				formatStr.append(str.substring(begin, end));
			} else {
				formatStr.append(str.substring(begin));
			}
		} while(end > 0);
		return ActionContextHelper.getText(clazz, formatStr.toString(), locale, args);
	}
}
